#include "xzip_file_system.h"
#include "xfile.h"
#include "xarchive.h"
#include "xfs.h"
#include "xfsm.h"
#include "xformat.h"
#include "xsys_log.h"
#include "xmem_utility.h"
#include "xallocator.h"
#include "xdata_structure.h"
#include "xge.h"

#include <zzip/zzip.h>
namespace{

  using namespace xge;
  using namespace xge::xge_internal;

  class ZipArchive;
  class ZipFileSystem;

  // XZip File System

  class ZipFile : public File {
  public:

    virtual int64_t Seek( int64_t offset , int position ) ;
    virtual size_t  Load( void* chunk , size_t size );
    virtual int64_t Length( );

    static int MapPosition( const int position ) {
      switch (position) {
      case POS_CUR : return SEEK_CUR;
      case POS_BEG : return SEEK_SET;
      case POS_END : return SEEK_END;
      default: XASSERT(0);return -1;
      }
    }
    // Create the file internally
    bool CreateFile( ZZIP_DIR* dir , const String& current_path , const String& name );

    virtual ~ZipFile() {}
  private:
    // A ZZip file handler to access the 
    // file in an zip archive
    ZZIP_FILE* zip_file_;
    // A template cache for length of this file
    int64_t length_;
    // Path name of this file
    String path_;
    friend class ZipArchive;
    friend class ZipFileSystem;
  };

  

  // ============= Implementation of ZipFile class
  // Create a file internally 
  bool ZipFile::CreateFile( ZZIP_DIR* dir , const String& current_path , const String& name ) {
    // Creating a file internally 
    // The callee can be a zip_root_archive or simply a virtual archive
    if( current_path.empty() ) 
      path_.assign(name);
    else {
      path_.reserve( current_path.size() + name.size() + 2 );
      path_ +=current_path;
      path_ +=name;
      XASSERT( current_path[ current_path.size()-1 ] == '/' );
    }// pathing building over
#ifdef XGE_USE_WIDE_CHAR
    // Converting the name of file into ANSI-8 string
    // awful story!
    const char_t* wbuf = path_.c_str();
    char buf[ MAX_CHAR_STACK_BUFFER_SIZE ];
    // The specific target path is using unicode
    // so we have to convert the name into the
    // MultiByte char , this is awful!
    int ret =
      ::WideCharToMultiByte( CP_ACP , 0 , wbuf , -1 ,buf,MAX_CHAR_STACK_BUFFER_SIZE,0,FALSE );
    if( ret<0 || ret >= MAX_CHAR_STACK_BUFFER_SIZE ) {
      size_t size = path_.size();
      char* heap_buffer = (char*)XMalloc( size*sizeof(char)+1 );
      ::WideCharToMultiByte( CP_ACP , 0 , wbuf , -1 ,heap_buffer,size+1,0,FALSE ) ;
      zip_file_ = zzip_file_open( dir , heap_buffer , ZZIP_ONLYZIP );
      XFree(heap_buffer);
    } else {
      zip_file_ = zzip_file_open( dir , buf , ZZIP_ONLYZIP );
    }
#else
    zip_file_ = zzip_file_open( dir , path_.c_str() , ZZIP_ONLYZIP );
#endif

    if( zip_file_ == NULL ) {
      SYS_LOG(PANIC,true,_xT("Can not open zip file!"));
      return false;
    }
    length_ = -1;
    return true;
  }
  int64_t ZipFile::Seek( int64_t offset , int position ) {
    // seek a file 
    // when reaching here the zip file can not be null pointer
    // This function is terribly slow ! For it have to decompress
    // the internal data to decide how many memory buffer needed
    // See the zzip_seek implementation quotation .
    // However,I've got no idea but to do so ...

    return static_cast<int64_t>( 
      zzip_seek( zip_file_ , static_cast<zzip_off_t>(offset) , MapPosition(position) ) );
  }

  int64_t ZipFile::Length() {
    // Length may be better to implement 
    // since we can take advantage of the
    // zzip_fstate
    if( length_ != -1 )
      return -1;
    ZZIP_STAT stat;
    int ret = zzip_fstat( zip_file_ , &stat );
    if( ret == -1 ) 
      return -1;
    else
      length_= static_cast< int64_t > ( stat.st_size );
    return length_;
  }

  size_t ZipFile::Load( void* chunk , size_t size ) {
    // Load the file into the chunk of buffer
    zzip_ssize_t rs = zzip_read( zip_file_ , chunk , size );
    if( rs <0 ) {
      // error occur
      ZZIP_DIR* dir = zzip_dirhandle( zip_file_ );
      // error goes here
      const char* error_str = zzip_strerror_of( dir );
      // can not read here 
#ifdef XGE_USE_WIDE_CHAR
      char_t buffer[MAX_CHAR_STACK_BUFFER_SIZE];
      ConvertAnsiStr2Unicode(error_str,buffer,MAX_CHAR_STACK_BUFFER_SIZE);
#else
      const char_t* buffer = error_str;
#endif
      // output error to the debug string
      SYS_LOG(PANIC,true,_xT("ZipFile::Load can not read from zip file!\nDescription:%s."),buffer);
      return -1;
    } 
    return rs;
  }
  // Implementation of the zip archive is very hard 
  // since the zip file do not contain the concept
  // of directory( inside of an zip archive ) , in
  // addition , the zzip library take advantage of
  // the char not wchar_t , so in part , our project
  // can not fully integrated into the zzip project.
  // We have faced many difficulties now , in order
  // to achieve better performance , we will take
  // advantage of the stream pointer inside a dir
  // stream .In the implementation of zzip function
  // the internal position of a zzip-dir-stream can
  // be performanced in O(1) complexity , that is a
  // good news for us . We expand the directory tree
  // as following :
  // if a zip archive layout is like this:
  // root--
  //       directory1
  //                 directory11
  //                 directory12
  //       directory2
  //                 directory21
  //                 directory22
  //       directory3
  //                  ...


  // ZipRootArchiveData will be initialized
  // when a zip-archive create
  class ZipRootArchiveData {
  public:
    typedef map< String , bool >::Map ArchiveTreeMap;
    const ArchiveTreeMap& archive_tree_map() const {
      return tree_;
    }
    ZZIP_DIR* zzip_dir() const {
      return zip_dir_;
    }
    const String& root_archive_name() const {
      return root_archive_name_;
    }
  public:
    bool Exist( const String& key  ) {
      // checking if this file is existed 
      // if it is in this archive,return
      // the zip file information 
      ArchiveTreeMap::iterator ret = tree_.find(key);
      return ret == tree_.end() ? false : true;
    }
  public:
    ZipRootArchiveData( ZZIP_DIR* dir , const String& name );
    ~ZipRootArchiveData();
  private:
    ZZIP_DIR* zip_dir_;
    ArchiveTreeMap tree_;
    String root_archive_name_;
  };

  ZipRootArchiveData::ZipRootArchiveData( ZZIP_DIR* dir , const String& name ) 
  :root_archive_name_(name) , zip_dir_(dir) {
    // I know i have to do something for this magnificent story as well as
    // this unbelievable touching
   ZZIP_DIRENT * dirent ;
   String fkey;
   while( (dirent = zzip_readdir(dir)) != NULL ) {
     // get the dirent to obtain the information of
     // this ZipRootArchiveData
     // We only store the archive inside a zip-archive
     if( dirent->st_size > 0 ) {
       // this is a file not a directory
#ifdef _DEBUG
       size_t ns = strlen( dirent->d_name );
       XASSERT( dirent->d_name[ns-1] != '/' );
#endif
       continue;
     }
#ifdef XGE_USE_WIDE_CHAR
     // doing th wide char conversion here
     size_t length = GetAnsiStr2UnicodeBufferSize(dirent->d_name);
     if( length<0 ) {
       SYS_LOG(PANIC,true,_xT("Can not convert name in ZipRootArchiveData::ZipRootArchiveData()"));
       continue;
     }
     fkey.resize(length+1);
#define string_as_array(string) &(*(string).begin())
     int l = ConvertAnsiStr2Unicode(dirent->d_name,string_as_array(fkey),length+1);
#undef string_as_array
     if( l <0 || l>=(int)length+1 ) {
       SYS_LOG(PANIC,true,_xT("Can not convert name in ZipRootArchiveData::ZipRootArchiveData()"));
       continue;
     }
     fkey.resize(l);
#else
     // name of a dirent zip archive
     fkey.assign(dirent->d_name);
#endif 
     std::pair < ArchiveTreeMap::iterator , bool > 
       insertion_ret = tree_.insert( std::make_pair(fkey,true) );
     XASSERT( insertion_ret.second );
   }
  }

  ZipRootArchiveData::~ZipRootArchiveData() {
    // destroy the internal buffer
    int ret = zzip_dir_close( zip_dir_ );
    if( ret<0 ) 
      SYS_LOG(WARNING,false,_T("zzip_dir_close() failed!"));
  }

  typedef SharedPtr< ZipRootArchiveData , DefaultDestructor<ZipRootArchiveData> > ZipRootArchiveDataPtr;

  // Implementing the ZipDirectory 
  class ZipArchive: public Archive{
  public:
    virtual FilePtr LoadFile( const char_t* file_path ) {
      ZipFile* file = new ZipFile();
      if( !file->CreateFile(internal_data_ptr_->zzip_dir(),relative_path_,file_path) ) {
        delete file;
        SYS_LOG(PANIC,true,_xT("Can not create zipfile!\nCurrent path:%s.\nArchive path:%s.\n"),
          relative_path_.c_str(),file_path);
        return FilePtr();
      }
      return FilePtr(file);
    }
    virtual ArchivePtr LoadArchive( const char_t* archive_path ) {
      ZipArchive* arc = new ZipArchive();
      if( !arc->CreateZipArchive(internal_data_ptr_,relative_path_,archive_path) ) {
        delete arc;
        SYS_LOG(PANIC,true,_xT("Can not create ziparchive!\nCurrent path:%s.\nArchive path:%s.\n"),
          relative_path_.c_str(),archive_path);
        return ArchivePtr();
      }
      return ArchivePtr(arc);
    }
    virtual DirPtr Begin() {
      // We do not provide this dir iterating
      // since it is time cosing implementation
      return DirPtr();
    }
    bool CreateZipArchive( const ZipRootArchiveDataPtr& ptr , 
      const String& relative_path , const char_t* postfix );
    bool CreateZipArchive( const String& zip_path );
  private:
    // if we have this internal data pointer
    // we can easily check weather a specific file 
    // is in this archive or this specific file is
    // an archive or not .
    ZipRootArchiveDataPtr internal_data_ptr_;
    // Current zip archive relative path prefix
    // this name must be ended as /
    // like : test/entry/ or if it is empty
    // therefore it must be the root zip archive
    String relative_path_;
  };

  bool ZipArchive::CreateZipArchive( const ZipRootArchiveDataPtr& ptr ,
    const String& relative_path , const char_t* postfix ) {
      if( !relative_path.empty() ) {
        XASSERT( relative_path[relative_path.size()-1] == _xT('/') );
        // this may cause memory allocation ,
        // however , we can not avoid this 
        relative_path_.assign(relative_path);
      }
      relative_path_.append(postfix);
      relative_path_+=_xT('/');
      // now we have building up or internal relative_path_ string
      if( !(ptr->Exist(relative_path_)) ) {
        // now the file archive is existed ( be sure )
        internal_data_ptr_.Reset(ptr);
        return true;
      }
      return false;
  }

  bool ZipArchive::CreateZipArchive( const String& zip_path ) {
    ZZIP_DIR* dir;
    zzip_error_t ec;
#ifdef XGE_USE_WIDE_CHAR
    const char_t* wbuf = zip_path.c_str();
    char buf[ MAX_CHAR_STACK_BUFFER_SIZE ];
    // The specific target path is using unicode
    // so we have to convert the name into the
    // MultiByte char , this is awful!
    int ret =
      ::WideCharToMultiByte( CP_ACP , 0 , wbuf , -1 ,buf,MAX_CHAR_STACK_BUFFER_SIZE,0,FALSE );
    if( ret<0 || ret >= MAX_CHAR_STACK_BUFFER_SIZE ) {
      size_t size = zip_path.size();
      char* heap_buffer = (char*)XMalloc( size*sizeof(char)+1 );
      ::WideCharToMultiByte( CP_ACP , 0 , wbuf , -1 ,heap_buffer,size+1,0,FALSE );
      dir = zzip_dir_open( heap_buffer , &ec );
      XFree(heap_buffer);
    } else {
      dir = zzip_dir_open( buf , &ec );
    }
#else
    dir = zzip_dir_open( zip_path.c_str() , &ec );
    // maping the error code to the string 
#endif
    if( dir == NULL ) {
      // error occur
      SYS_LOG(PANIC,true,_xT("Can not open zip archive!\nArchive:%s.\nError code:%d!"),zip_path.c_str(),ec);
      return false;
    }
    internal_data_ptr_.Reset( new ZipRootArchiveData(dir,zip_path) );
    return true;
  }


  class ZipFileSystem : public FileSystem {
  public:
    virtual ArchivePtr LoadArchive( const char_t* path_name ) {
      ZipArchive* arc = new ZipArchive();
      if( !arc->CreateZipArchive(path_name) ) {
        // can not load
        delete arc;
        return ArchivePtr();
      }
      return ArchivePtr(arc);
    }
    // The Local File System MUST initialized at
    // the very first
    virtual bool IsArchiveExisted( const String& path ) {
      return local_file_system_->IsExisted(path);
    }
    ZipFileSystem(): FileSystem( GetZipFileSystemName() ) {}
  public:
    // The following function can not be supported
    virtual bool IsFileExisted( const String& path ) {
      // Can not implemented
      return false;
    }

    virtual bool IsExisted(  const String& path ) {
      return false;
    }

    virtual FilePtr LoadFile( const char_t* path_name ) {
      return FilePtr();
    }

  public:
    // Double initialization 
    bool CreateZipFileSystem() {
      local_file_system_ = XGameEngine::GetFileSystemManager()->GetFileSystem(GetLocalFileSystemName());
      if( local_file_system_.Null() ) 
        return false;
      return true;
    }
  private:
    FileSystemPtr local_file_system_;
  };


}// namespace

namespace xge{
namespace xge_internal{
  // Register the file system into the FileSystemManager
  bool RegisterZipFileSystem() {
    // Register Zip File System
    ZipFileSystem* zip_fs = new ZipFileSystem();
    if( !zip_fs->CreateZipFileSystem() ) {
      delete zip_fs;
      return false;
    }
    if( !XGameEngine::GetFileSystemManager()->RegisterFileSystem( zip_fs ) ) 
      return false;
    return true;
  }
}// namespace xge
}// namespace xge_internal

